package main

import (
	"fmt"
	"reflect"
)

// 基于反射的代码是极其脆弱的，反射中的类型错误会在真正运行的时候才回引发panic
// 大量使用反射的代码通常难以理解

// Student 结构体
type Student struct {
	Name  string `json:"name" form:"username"`
	Age   int    `json:"age"`
	Score int    `json:"score"`
}

func (s *Student) SetInfo(name string, age int, score int) {
	s.Name = name
	s.Age = age
	s.Score = score
}

func (s Student) Print() {
	fmt.Println("这是一个打印方法")
}

func (s Student) GetInfo() string {
	var str = fmt.Sprintf("姓名：%v 年龄：%v 成绩：%v", s.Name, s.Age, s.Score)
	return str
}

func PrintStructField(s interface{}) {

	t := reflect.TypeOf(s)

	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的参数不是结构体")
		return
	}

	// 1. 通过类型变量里面的Field可以获取结构体的字段
	namefield := t.Field(0) // 返回reflect.StructField
	fmt.Println(namefield)
	fmt.Println(namefield.Name)            // 获取字段名
	fmt.Println(namefield.Type)            // 获取字段值类型
	fmt.Println(namefield.Tag.Get("json")) // 获取tag标签
	fmt.Println(namefield.Tag.Get("form")) // 获取tag标签

	// 2. 通过FieldByName
	agefield, ok := t.FieldByName("Age")
	if ok {
		fmt.Println(agefield)
		fmt.Println(agefield.Name)            // 获取字段名
		fmt.Println(agefield.Type)            // 获取字段值类型
		fmt.Println(agefield.Tag.Get("json")) // 获取tag标签
		fmt.Println(agefield.Tag.Get("form")) // 获取tag标签
	}

	// 3. 通过NumField获取结构体有多少个字段
	var fieldCount = t.NumField()
	fmt.Printf("机构体t有%v个属性\n", fieldCount)

	// 4. 通过值变量获取结构体属性对应的值
	v := reflect.ValueOf(s)
	fmt.Println(v.FieldByName("Name"))
	fmt.Println(v.FieldByName("Age"))

	// 5. for循环遍历
	for i := 0; i < fieldCount; i++ {
		fmt.Printf("属性名称：%v，属性值：%v，属性类型：%v\n", t.Field(i).Name, v.Field(i), t.Field(i).Type)
	}
}

func PrintStructFn(s interface{}) {
	t := reflect.TypeOf(s)

	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的参数不是结构体")
		return
	}

	// 获取结构体里的方法（Method）
	method0 := t.Method(0) // 索引的大小顺序只和方法名的ACSII码有关
	fmt.Println(method0)
	fmt.Println(method0.Name)
	fmt.Println(method0.Type)

	method1, ok := t.MethodByName("Print")
	if ok {
		fmt.Println(method1)
		fmt.Println(method1.Name)
		fmt.Println(method1.Type)
	}

	// 获取方法数量
	fmt.Println(t.NumMethod())

	// 执行结构体中的方法
	v := reflect.ValueOf(s)
	v.MethodByName("Print").Call(nil)           // 没有参数的方法需要传入nil
	info := v.MethodByName("GetInfo").Call(nil) // 为什么是返回切片？
	fmt.Println(info[0])

	// 执行带参数的方法
	var params []reflect.Value
	params = append(params, reflect.ValueOf("李四"))
	params = append(params, reflect.ValueOf(20))
	params = append(params, reflect.ValueOf(95))
	v.MethodByName("SetInfo").Call(params)
	info = v.MethodByName("GetInfo").Call(nil)
	fmt.Println(info[0])

}

func reflectChangeStruct(s interface{}) {
	t := reflect.TypeOf(s)

	// 要对结构体进行修改，必须传入结构体指针
	if t.Kind() != reflect.Ptr {
		fmt.Println("传入的参数不是指针类型")
		return
	} else if t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的不是结构体指针类型")
		return
	}

	// 修改结构体属性
	v := reflect.ValueOf(s)
	name := v.Elem().FieldByName("Name")
	name.SetString("小李")
	age := v.Elem().FieldByName("Age")
	age.SetInt(60)
}

func main() {
	stu1 := Student{
		Name:  "小明",
		Age:   15,
		Score: 98,
	}

	// PrintStructField(stu1)
	// PrintStructFn(&stu1)
	fmt.Println(stu1)
	reflectChangeStruct(&stu1)
	fmt.Println(stu1)
}
